// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io"
	"log"
	"os"
	"sort"

	"cmd/internal/edit"
	"cmd/internal/objabi"
)

const usageMessage = "" +
	`Usage of 'go tool cover':
Given a coverage profile produced by 'go test':
	go test -coverprofile=c.out

Open a web browser displaying annotated source code:
	go tool cover -html=c.out

Write out an HTML file instead of launching a web browser:
	go tool cover -html=c.out -o coverage.html

Display coverage percentages to stdout for each function:
	go tool cover -func=c.out

Finally, to generate modified source code with coverage annotations
(what go test -cover does):
	go tool cover -mode=set -var=CoverageVariableName program.go
`

func usage() {
	fmt.Fprint(os.Stderr, usageMessage)
	fmt.Fprintln(os.Stderr, "\nFlags:")
	flag.PrintDefaults()
	fmt.Fprintln(os.Stderr, "\n  Only one of -html, -func, or -mode may be set.")
	os.Exit(2)
}

var (
	mode    = flag.String("mode", "", "coverage mode: set, count, atomic")
	varVar  = flag.String("var", "GoCover", "name of coverage variable to generate")
	output  = flag.String("o", "", "file for output; default: stdout")
	htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
	funcOut = flag.String("func", "", "output coverage profile information for each function")
)

var profile string // 要阅读的配置文件；没有参数时，-html或-func 

var counterStmt func(*File, string) string

const (
	atomicPackagePath = "sync/atomic"
	atomicPackageName = "_cover_atomic_"
)

func main() {
	objabi.AddVersionFlag()
	flag.Usage = usage
	flag.Parse()

	// 的值。
	if flag.NFlag() == 0 && flag.NArg() == 0 {
		flag.Usage()
	}

	err := parseFlags()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
		os.Exit(2)
	}

	// 生成覆盖率注释源。
	if *mode != "" {
		annotate(flag.Arg(0))
		return
	}

	// 输出HTML或函数覆盖信息。
	if *htmlOut != "" {
		err = htmlOutput(profile, *output)
	} else {
		err = funcOutput(profile, *output)
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "cover: %v\n", err)
		os.Exit(2)
	}
}

// parseFlags设置概要文件和计数器TMT全局文件，并执行验证。
func parseFlags() error {
	profile = *htmlOut
	if *funcOut != "" {
		if profile != "" {
			return fmt.Errorf("too many options")
		}
		profile = *funcOut
	}

	// 必须显示配置文件或重写Go source。
	if (profile == "") == (*mode == "") {
		return fmt.Errorf("too many options")
	}

	if *varVar != "" && !token.IsIdentifier(*varVar) {
		return fmt.Errorf("-var: %q is not a valid identifier", *varVar)
	}

	if *mode != "" {
		switch *mode {
		case "set":
			counterStmt = setCounterStmt
		case "count":
			counterStmt = incCounterStmt
		case "atomic":
			counterStmt = atomicCounterStmt
		default:
			return fmt.Errorf("unknown -mode %v", *mode)
		}

		if flag.NArg() == 0 {
			return fmt.Errorf("missing source file")
		} else if flag.NArg() == 1 {
			return nil
		}
	} else if flag.NArg() == 0 {
		return nil
	}
	return fmt.Errorf("too many arguments")
}

// Block表示要在分析中记录的基本块的信息。
// 注：我们对基本块的定义基于控制结构；我们不会把
// 分开和| |。我们可以，但这似乎还不够重要。
type Block struct {
	startByte token.Pos
	endByte   token.Pos
	numStmt   int
}

// File是解析器中使用的文件状态的包装器。
// 基本解析树遍历器就是这种类型的方法。
type File struct {
	fset    *token.FileSet
	name    string // 文件名。
	astFile *ast.File
	blocks  []Block
	content []byte
	edit    *edit.Buffer
}

// findText在原始源代码中查找文本，从位置开始。
// 它正确跳过注释，并假设它不需要处理带引号的字符串。
// 返回f.src中的字节偏移量。
func (f *File) findText(pos token.Pos, text string) int {
	b := []byte(text)
	start := f.offset(pos)
	i := start
	s := f.content
	for i < len(s) {
		if bytes.HasPrefix(s[i:], b) {
			return i
		}
		if i+2 <= len(s) && s[i] == '/' && s[i+1] == '/' {
			for i < len(s) && s[i] != '\n' {
				i++
			}
			continue
		}
		if i+2 <= len(s) && s[i] == '/' && s[i+1] == '*' {
			for i += 2; ; i++ {
				if i+2 > len(s) {
					return 0
				}
				if s[i] == '*' && s[i+1] == '/' {
					i += 2
					break
				}
			}
			continue
		}
		i++
	}
	return -1
}

// Visit实现了ast。访客界面。
func (f *File) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.BlockStmt:
		// 如果是switch或select，则主体是case子句列表；不要给积木贴标签。
		if len(n.List) > 0 {
			switch n.List[0].(type) {
			case *ast.CaseClause: // 切换
				for _, n := range n.List {
					clause := n.(*ast.CaseClause)
					f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
				}
				return f
			case *ast.CommClause: // 选择
				for _, n := range n.List {
					clause := n.(*ast.CommClause)
					f.addCounters(clause.Colon+1, clause.Colon+1, clause.End(), clause.Body, false)
				}
				return f
			}
		}
		f.addCounters(n.Lbrace, n.Lbrace+1, n.Rbrace+1, n.List, true) // /+1跨过右大括号。
	case *ast.IfStmt:
		if n.Init != nil {
			ast.Walk(f, n.Init)
		}
		ast.Walk(f, n.Cond)
		ast.Walk(f, n.Body)
		if n.Else == nil {
			return nil
		}
		// 其他的都是特别的，因为如果我们有
		// 如果x{
		// /}或者如果y{
		// /}
		// 我们想涵盖“如果y”。要做到这一点，我们需要一个放置计数器的地方，
		// 所以我们添加了一个隐藏块：
		// 如果x{
		// /}或者{
		// 如果y{
		// /}
		// /}
		elseOffset := f.findText(n.Body.End(), "else")
		if elseOffset < 0 {
			panic("lost else")
		}
		f.edit.Insert(elseOffset+4, "{")
		f.edit.Insert(f.offset(n.Else.End()), "}")

		// 我们刚刚创建了一个块，现在就走吧。
		// 调整新块的位置，使其在
		// 之后开始。这将导致它跟随“{”
		// 我们在上面插入了。
		pos := f.fset.File(n.Body.End()).Pos(elseOffset + 4)
		switch stmt := n.Else.(type) {
		case *ast.IfStmt:
			block := &ast.BlockStmt{
				Lbrace: pos,
				List:   []ast.Stmt{stmt},
				Rbrace: stmt.End(),
			}
			n.Else = block
		case *ast.BlockStmt:
			stmt.Lbrace = pos
		default:
			panic("unexpected node type in if")
		}
		ast.Walk(f, n.Else)
		return nil
	case *ast.SelectStmt:
		// 不注释空的select-创建语法错误。
		if n.Body == nil || len(n.Body.List) == 0 {
			return nil
		}
	case *ast.SwitchStmt:
		// 不要注释空开关-创建语法错误。
		if n.Body == nil || len(n.Body.List) == 0 {
			if n.Init != nil {
				ast.Walk(f, n.Init)
			}
			if n.Tag != nil {
				ast.Walk(f, n.Tag)
			}
			return nil
		}
	case *ast.TypeSwitchStmt:
		// 不注释空类型开关-创建语法错误。
		if n.Body == nil || len(n.Body.List) == 0 {
			if n.Init != nil {
				ast.Walk(f, n.Init)
			}
			ast.Walk(f, n.Assign)
			return nil
		}
	case *ast.FuncDecl:
		// 不要用空名称注释函数——它们无法执行。
		if n.Name.Name == "_" {
			return nil
		}
	}
	return f
}

func annotate(name string) {
	fset := token.NewFileSet()
	content, err := os.ReadFile(name)
	if err != nil {
		log.Fatalf("cover: %s: %s", name, err)
	}
	parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
	if err != nil {
		log.Fatalf("cover: %s: %s", name, err)
	}

	file := &File{
		fset:    fset,
		name:    name,
		content: content,
		edit:    edit.NewBuffer(content),
		astFile: parsedFile,
	}
	if *mode == "atomic" {
		// 在package子句之后立即添加sync/atomic的导入。
		// 即使存在现有的导入，我们也会这样做，因为现有的导入可能会在我们希望
		// 引用它的任何给定位置被隐藏，而我们的名字（_cover_atomic_）不太可能被隐藏。
		file.edit.Insert(file.offset(file.astFile.Name.End()),
			fmt.Sprintf("; import %s %q", atomicPackageName, atomicPackagePath))
	}

	ast.Walk(file, file.astFile)
	newContent := file.edit.Bytes()

	fd := os.Stdout
	if *output != "" {
		var err error
		fd, err = os.Create(*output)
		if err != nil {
			log.Fatalf("cover: %s", err)
		}
	}

	fmt.Fprintf(fd, "// 行%s:1\n”，name）
	fd.Write(newContent)

	// 打印源树后，为计数器等添加一些声明。
	// 我们可以通过添加到树中来实现这一点，但只打印文本更容易。
	file.addVariables(fd)
}

// setCounterStmt返回表达式：u count[23]=1。
func setCounterStmt(f *File, counter string) string {
	return fmt.Sprintf("%s = 1", counter)
}

// incCounterStmt返回表达式：u count[23]++. 
func incCounterStmt(f *File, counter string) string {
	return fmt.Sprintf("%s++", counter)
}

// atomicCounterStmt返回表达式：atomic。AddUint32（&_count[23]，1）
func atomicCounterStmt(f *File, counter string) string {
	return fmt.Sprintf("%s.AddUint32(&%s, 1)", atomicPackageName, counter)
}

// newCounter以适当的形式创建一个新的计数器表达式。
func (f *File) newCounter(start, end token.Pos, numStmt int) string {
	stmt := counterStmt(f, fmt.Sprintf("%s.Count[%d]", *varVar, len(f.blocks)))
	f.blocks = append(f.blocks, Block{start, end, numStmt})
	return stmt
}

// addCounters获取语句列表，并将计数器添加到该列表顶层的每个基本块的开头。例如，给定
// 
// S1 
// if条件{
// S2 
// }
// S3 
// 
// 计数器将添加到S1和S3之前。包含S2 
// 的块将在单独的调用中访问。
// TODO:嵌套的简单块会得到不必要（但正确）的计数器
func (f *File) addCounters(pos, insertPos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) {
	// 特殊情况：确保向空块添加计数器。不能在
	// 下面执行此操作，否则我们将在返回语句之后的空语句列表中添加一个计数器。
	if len(list) == 0 {
		f.edit.Insert(f.offset(insertPos), f.newCounter(insertPos, blockEnd, 0)+";")
		return
	}
	// 复制该列表，因为我们可能会对其进行变异，并且应该保持现有列表的完整性。
	list = append([]ast.Stmt(nil), list...)
	// 我们有一个块（语句列表），但它可能有几个基本块，因为
	// 语句的出现会影响控制流。
	for {
		// 查找影响控制流的第一条语句（break、continue、if等）。
		// 这将是这个基本块的最后一个语句。
		var last int
		end := blockEnd
		for last = 0; last < len(list); last++ {
			stmt := list[last]
			end = f.statementBoundary(stmt)
			if f.endsBasicSourceBlock(stmt) {
				// 如果它是一个带标签的语句，我们需要在
				// 标签和它的语句之间放置一个计数器，因为它可能是goto 
				// 然后开始一个基本块。也就是说，给定
				// foo:stmt 
				// 我们需要创建
				// foo:；stmt 
				// 并将标签标记为块终止语句。
				// 结果将是
				// foo:COUNTER[n]++；stmt 
				// 但是，如果带标签的语句已经是
				// 控制语句，例如带标签的for，我们就不能这样做。
				if label, isLabel := stmt.(*ast.LabeledStmt); isLabel && !f.isControl(label.Stmt) {
					newLabel := *label
					newLabel.Stmt = &ast.EmptyStmt{
						Semicolon: label.Stmt.Pos(),
						Implicit:  true,
					}
					end = label.Pos() // 上一个块在标签前结束。
					list[last] = &newLabel
					// 打开一个空白，插入旧语句，现在没有标签。
					list = append(list, nil)
					copy(list[last+1:], list[last:])
					list[last+1] = label.Stmt
				}
				last++
				extendToClosingBrace = false // 区块现在已断开。
				break
			}
		}
		if extendToClosingBrace {
			end = blockEnd
		}
		if pos != end { // 如果区块邻接，则可能没有可覆盖的来源。
			f.edit.Insert(f.offset(insertPos), f.newCounter(pos, end, last)+";")
		}
		list = list[last:]
		if len(list) == 0 {
			break
		}
		pos = list[0].Pos()
		insertPos = pos
	}
}

// hasFuncLiteral报告节点中第一个func literal 
// 的存在和位置（如果有）。如果出现func文字，通常会标记基本块的终止
// 因为函数体本身就是一个块。
// 因此，我们在找到的第一个函数文本的正文开始处画一条线。
// TODO:如果不止一个呢？可能没什么大不了的。
func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
	if n == nil {
		return false, 0
	}
	var literal funcLitFinder
	ast.Walk(&literal, n)
	return literal.found(), token.Pos(literal)
}

// 语句边界在s中查找终止源中当前基本
// 块的位置。
func (f *File) statementBoundary(s ast.Stmt) token.Pos {
	// 控制流语句很简单。
	switch s := s.(type) {
	case *ast.BlockStmt:
		// 像对待基本块一样对待块，以避免计数器重叠。
		return s.Lbrace
	case *ast.IfStmt:
		found, pos := hasFuncLiteral(s.Init)
		if found {
			return pos
		}
		found, pos = hasFuncLiteral(s.Cond)
		if found {
			return pos
		}
		return s.Body.Lbrace
	case *ast.ForStmt:
		found, pos := hasFuncLiteral(s.Init)
		if found {
			return pos
		}
		found, pos = hasFuncLiteral(s.Cond)
		if found {
			return pos
		}
		found, pos = hasFuncLiteral(s.Post)
		if found {
			return pos
		}
		return s.Body.Lbrace
	case *ast.LabeledStmt:
		return f.statementBoundary(s.Stmt)
	case *ast.RangeStmt:
		found, pos := hasFuncLiteral(s.X)
		if found {
			return pos
		}
		return s.Body.Lbrace
	case *ast.SwitchStmt:
		found, pos := hasFuncLiteral(s.Init)
		if found {
			return pos
		}
		found, pos = hasFuncLiteral(s.Tag)
		if found {
			return pos
		}
		return s.Body.Lbrace
	case *ast.SelectStmt:
		return s.Body.Lbrace
	case *ast.TypeSwitchStmt:
		found, pos := hasFuncLiteral(s.Init)
		if found {
			return pos
		}
		return s.Body.Lbrace
	}
	// 如果不是控制流语句，那么它是一个声明、表达式、调用等，并且可能有一个函数文本。
	// 如果是这样，那就很棘手了，因为我们想从这个块中排除函数体。
	// 在我们找到的第一个函数文本的正文开头画一条线。
	// TODO:如果不止一个呢？可能没什么大不了的。
	found, pos := hasFuncLiteral(s)
	if found {
		return pos
	}
	return s.End()
}

// endsBasicSourceBlock报告s是否更改控制流：break、if等，
// 或者如果它只是有问题，例如包含一个函数literal，由于表达式中的块，这将使
// 计算复杂化。
func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
	switch s := s.(type) {
	case *ast.BlockStmt:
		// 像对待基本块一样对待块，以避免计数器重叠。
		return true
	case *ast.BranchStmt:
		return true
	case *ast.ForStmt:
		return true
	case *ast.IfStmt:
		return true
	case *ast.LabeledStmt:
		return true // 一个goto可能会在这里分支，开始一个新的基本区块。
	case *ast.RangeStmt:
		return true
	case *ast.SwitchStmt:
		return true
	case *ast.SelectStmt:
		return true
	case *ast.TypeSwitchStmt:
		return true
	case *ast.ExprStmt:
		// 紧急呼叫改变流量。
		// 我们真的应该验证“panic”是预定义的函数，
		// 但是如果不进行类型检查，我们就不能验证，而且它被
		// 实际问题的可能性非常小。
		if call, ok := s.X.(*ast.CallExpr); ok {
			if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
				return true
			}
		}
	}
	found, _ := hasFuncLiteral(s)
	return found
}

// isControl报告s是否是一个控制语句，如果已标记，则不能与其标签分离。
func (f *File) isControl(s ast.Stmt) bool {
	switch s.(type) {
	case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
		return true
	}
	return false
}

// funcLitFinder实现了ast。Visitor模式查找任何
// 函数文本在子树中的位置。
type funcLitFinder token.Pos

func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
	if f.found() {
		return nil // 删除搜索。
	}
	switch n := node.(type) {
	case *ast.FuncLit:
		*f = funcLitFinder(n.Body.Lbrace)
		return nil // 删除搜索。
	}
	return f
}

func (f *funcLitFinder) found() bool {
	return token.Pos(*f) != token.NoPos
}

// 用于[]块1的排序接口；用于在addVariables中进行自检。

type block1 struct {
	Block
	index int
}

type blockSlice []block1

func (b blockSlice) Len() int           { return len(b) }
func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
func (b blockSlice) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }

// 偏移量将令牌位置转换为0索引字节偏移量。
func (f *File) offset(pos token.Pos) int {
	return f.fset.Position(pos).Offset
}

// addVariables将声明添加到文件末尾，以设置计数器和位置变量。
func (f *File) addVariables(w io.Writer) {
	// 自检：验证插入指令的基本块是否不相交。
	t := make([]block1, len(f.blocks))
	for i := range f.blocks {
		t[i].Block = f.blocks[i]
		t[i].index = i
	}
	sort.Sort(blockSlice(t))
	for i := 1; i < len(t); i++ {
		if t[i-1].endByte > t[i].startByte {
			fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
			// 注意：错误消息位于字节位置，而不是令牌位置。
			fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
				f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
				f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
		}
	}

	// 将coverage结构声明为包级别变量。
	fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
	fmt.Fprintf(w, "\tCount     [%d]uint32\n", len(f.blocks))
	fmt.Fprintf(w, "\tPos       [3 * %d]uint32\n", len(f.blocks))
	fmt.Fprintf(w, "\tNumStmt   [%d]uint16\n", len(f.blocks))
	fmt.Fprintf(w, "} {\n")

	// 初始化位置数组字段。
	fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))

	// 职位列表很长。每个位置编码如下，以减小大小：
	// /-32位起始行号
	// /-32位结束行号
	// /-（16位结束列号<<16）|（16位起始列号）。
	for i, block := range f.blocks {
		start := f.fset.Position(block.startByte)
		end := f.fset.Position(block.endByte)

		start, end = dedup(start, end)

		fmt.Fprintf(w, "\t\t%d, %d, %#x, // /[%d]\n“，start.Line，end.Line，（end.Column&0xFFFF）<<16|（start.Column&0xFFFF），i）
	}

	// 关闭位置数组。
	fmt.Fprintf(w, "\t},\n")

	// 初始化位置数组字段。
	fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))

	// 每个块的语句列表很长，因此我们可以给出一个常规的
	// 覆盖百分比的估值“.为了节省空间，它是一个16位的数字，所以我们
	// 如果它溢出，将其钳制-在实践中无关紧要。
	for i, block := range f.blocks {
		n := block.numStmt
		if n > 1<<16-1 {
			n = 1<<16 - 1
		}
		fmt.Fprintf(w, "\t\t%d, // /%d\n”，n，i）
	}

	// 关闭每个块数组的语句。
	fmt.Fprintf(w, "\t},\n")

	// 关闭结构初始化。
	fmt.Fprintf(w, "}\n")

	// 发出对原子包的引用，以避免文件中没有代码时出现导入和未使用错误。
	if *mode == "atomic" {
		fmt.Fprintf(w, "var _ = %s.LoadUint32\n", atomicPackageName)
	}
}

// 当有一行
// 指令未指定列信息且输入
// 未通过gofmt时，位置可能会重复。
// 见问题#27530和#30746。
// 测试是TestHtmlUnformatted和TestLineDup。
// 我们使用地图来避免重复。

// pos2是一对令牌。位置值，用作贴图键类型。
type pos2 struct {
	p1, p2 token.Position
}

// seenps2跟踪我们是否看到代币。位置对。
var seenPos2 = make(map[pos2]bool)

// 重复数据消除需要一个令牌。定位pair并返回一个不与任何现有pair重复的pair。返回的对将清除偏移量
// 字段。
func dedup(p1, p2 token.Position) (r1, r2 token.Position) {
	key := pos2{
		p1: p1,
		p2: p2,
	}

	// 我们想忽略地图中的偏移量字段，因为cover只使用file/line/column。
	key.p1.Offset = 0
	key.p2.Offset = 0

	for seenPos2[key] {
		key.p2.Column++
	}
	seenPos2[key] = true

	return key.p1, key.p2
}
